package org.javaparser.template;

import japa.parser.ASTHelper;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.FieldDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.body.VariableDeclarator;
import japa.parser.ast.body.VariableDeclaratorId;
import japa.parser.ast.stmt.BlockStmt;
import japa.parser.ast.type.ClassOrInterfaceType;

import java.util.ArrayList;
import java.util.List;

import org.javaparser.template.utils.JavaParserUtils;

public class JavaParserTemplate {
	private CompilationUnit cu;
	private String currentPackageName;

	public JavaParserTemplate(CompilationUnit cu) {
		this.cu = cu;
	}

	public void addTypeInImport(String typeName) {
		JavaParserUtils.addTypeInImport(cu, currentPackageName, typeName);
	}
	
	private String getUsedTypeName(String typeName) {
		return JavaParserUtils.getUsedTypeName(cu, currentPackageName, typeName);
	}
		
	private ClassOrInterfaceDeclaration createType(String packageName,
					String typeName, int visibility, boolean isInterface,
					List<String> extendsClassNames, List<String> implementsInterfaceNames) {
		//Package
		if (packageName!=null && !"".equals(packageName.trim())) {
			cu.setPackage(new PackageDeclaration(ASTHelper.createNameExpr(packageName)));
			this.currentPackageName = packageName;
		}
		
		//Type definition
		String usedTypeName = getUsedTypeName(typeName);
		ClassOrInterfaceDeclaration type = new ClassOrInterfaceDeclaration(
				JavaParserUtils.getModifier(visibility), isInterface, usedTypeName);
		
		//Type extends
		List<ClassOrInterfaceType> extendsList = new ArrayList<ClassOrInterfaceType>();
		for (String extendsClassName : extendsClassNames) {
			String usedExtendsClassName = getUsedTypeName(extendsClassName);
			ClassOrInterfaceType extendsType = new ClassOrInterfaceType(usedExtendsClassName);
			extendsList.add(extendsType);
		}
		if (extendsList.size()>0) {
			type.setExtends(extendsList);
		}

		//Type implements
		List<ClassOrInterfaceType> implementsList = new ArrayList<ClassOrInterfaceType>();
		for (String implementsClassName : implementsInterfaceNames) {
			String usedImplementsClassName = getUsedTypeName(implementsClassName);
			ClassOrInterfaceType extendsType = new ClassOrInterfaceType(usedImplementsClassName);
			implementsList.add(extendsType);
		}
		if (implementsList.size()>0) {
			type.setImplements(implementsList);
		}

		ASTHelper.addTypeDeclaration(cu, type);

		return type;
	}

	public ClassOrInterfaceDeclaration createClass(
							String packageName, String className, int visibility) {
		return createClass(packageName, className, visibility, null, new ArrayList<String>());
	}

	public ClassOrInterfaceDeclaration createClass(
							String packageName, String className,
							int visibility, String extendsClassName) {
		return createClass(packageName, className, visibility, extendsClassName, new ArrayList<String>());
	}

	public ClassOrInterfaceDeclaration createClass(
							String packageName, String className, int visibility,
							List<String> implementsInterfaceNames) {
		return createClass(packageName, className, visibility, null, implementsInterfaceNames);
	}

	public ClassOrInterfaceDeclaration createClass(
					String packageName, String className, int visibility,
					String extendsClassName, List<String> implementsInterfaceNames) {
		List<String> extendsClassNames = new ArrayList<String>();
		if (extendsClassName!=null) {
			extendsClassNames.add(extendsClassName);
		}
		return createType(packageName, className, visibility, false,
					extendsClassNames, implementsInterfaceNames);
	}
	
	public ClassOrInterfaceDeclaration createInterface(
					String packageName, String className, int visibility) {
		return createInterface(packageName, className, visibility, new ArrayList<String>());
	}

	public ClassOrInterfaceDeclaration createInterface(
					String packageName, String className, int visibility,
					List<String> extendsInterfaceNames) {
		return createType(packageName, className, visibility, false,
				extendsInterfaceNames, new ArrayList<String>());
	}
	
	public void addField(ClassOrInterfaceDeclaration type, 
					String fieldName, int visibility, String fieldTypeName) {
		String usedFieldTypeName = getUsedTypeName(fieldTypeName);
		ClassOrInterfaceType fieldType = new ClassOrInterfaceType(usedFieldTypeName);
        List<VariableDeclarator> fieldInitializations = new ArrayList<VariableDeclarator>();
        //MethodCallExpr initList = new MethodCallExpr(null, "new ArrayList<"+listElementType+">");
        fieldInitializations.add(new VariableDeclarator(new VariableDeclaratorId(fieldName), null));
		FieldDeclaration field = new FieldDeclaration(
				JavaParserUtils.getModifier(visibility), fieldType, fieldInitializations);

        ASTHelper.addMember(type, field);
	}
	
	public void addMethod(ClassOrInterfaceDeclaration type, String methodName, int visibility,
			List<MethodParameter> parameters, MethodCreatorCallback callback) {
		addMethod(type, methodName, visibility, parameters, null, callback);
	}

	public void addMethod(ClassOrInterfaceDeclaration type, String methodName, int visibility,
			String returnTypeName, MethodCreatorCallback callback) {
		addMethod(type, methodName, visibility, new ArrayList<MethodParameter>(), callback);
	}
	
	public void addMethod(ClassOrInterfaceDeclaration type, String methodName, int visibility,
			List<MethodParameter> parameters, String returnTypeName, MethodCreatorCallback callback) {
		MethodDeclaration method = null;
		if (returnTypeName!=null) {
			String usedReturnTypeName = getUsedTypeName(returnTypeName);
			ClassOrInterfaceType returnType = new ClassOrInterfaceType(usedReturnTypeName); 
			method = new MethodDeclaration(
					JavaParserUtils.getModifier(visibility), returnType, methodName);
		} else {
			method = new MethodDeclaration(
					JavaParserUtils.getModifier(visibility), ASTHelper.VOID_TYPE, methodName);
		}
		
		int cpt = 0;
		for (MethodParameter methodParameter : parameters) {
			String usedParameterReturnTypeName = getUsedTypeName(methodParameter.getTypeName());
			Parameter param = ASTHelper.createParameter(
						ASTHelper.createReferenceType(usedParameterReturnTypeName, cpt),
						methodParameter.getParameterName());
	        param.setVarArgs(false);
	        ASTHelper.addParameter(method, param);
	        cpt++;
		}
        
        ASTHelper.addMember(type, method);

        BlockStmt block = new BlockStmt();
        method.setBody(block);
        
        callback.addBody(cu, type, block);
	}

	public void addGetBeanMethod(ClassOrInterfaceDeclaration type,
							final String fieldName, String fieldTypeName) {
		String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		addMethod(type, methodName, JavaParserUtils.getModifier(Visibility.PUBLIC),
				new ArrayList<MethodParameter>(), fieldTypeName, new MethodCreatorCallback() {
			public void addBody(CompilationUnit cu, ClassOrInterfaceDeclaration type, BlockStmt block) {
				MethodCodeWrapper wrapper = new MethodCodeWrapper(cu, type, block);
				wrapper.addVariableReturn(fieldName, true);
			}
		});
	}

	public void addSimpleGetBeanMethodWithStringReturn(ClassOrInterfaceDeclaration type,
			int visibility, String methodName, final String returnValue) {
		addMethod(type, methodName, JavaParserUtils.getModifier(visibility),
				new ArrayList<MethodParameter>(), "String", new MethodCreatorCallback() {
			public void addBody(CompilationUnit cu, ClassOrInterfaceDeclaration type, BlockStmt block) {
				MethodCodeWrapper wrapper = new MethodCodeWrapper(cu, type, block);
				wrapper.addStringReturn(returnValue);
			}
		});
	}

	public void addSetBeanMethod(ClassOrInterfaceDeclaration type, final String fieldName, String fieldTypeName) {
		String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
		List<MethodParameter> parameters = new ArrayList<MethodParameter>();
		parameters.add(new MethodParameter(fieldTypeName, fieldName));
		addMethod(type, methodName, JavaParserUtils.getModifier(Visibility.PUBLIC),
				parameters, fieldTypeName, new MethodCreatorCallback() {
			public void addBody(CompilationUnit cu, ClassOrInterfaceDeclaration type, BlockStmt block) {
				MethodCodeWrapper wrapper = new MethodCodeWrapper(cu, type, block);
				wrapper.addVariableAffectation(fieldName, true, fieldName, false);
			}
		});
	}
	
	public void addBeanField(ClassOrInterfaceDeclaration type,
							String fieldName, String fieldTypeName) {
		addField(type, fieldName, Visibility.PRIVATE, fieldTypeName);
		addGetBeanMethod(type, fieldName, fieldTypeName);
		addSetBeanMethod(type, fieldName, fieldTypeName);
	}
}
